from email.mime.text import MIMEText
import smtplib
from queue import Queue
import json
import datetime as dt
import re

__wecom_user = None
__wecom_psw = None
_mail_box_queue = Queue(128)

_mail_process_running = True
_h_console_out = False
_h_client = None
_h_logging = None
_blank_replacer = re.compile("[^\S\n]")


def setups(console_out_, client_, logging_):
    global _h_console_out, _h_client, _h_logging
    _h_console_out = console_out_
    _h_client = client_
    _h_logging = logging_


def setup_mail(user, psw):
    global __wecom_user, __wecom_psw
    __wecom_user = user
    __wecom_psw = psw


def turn_off_process(yes=True):
    global _mail_process_running
    _mail_process_running = not yes
    _mail_box_queue.put_nowait((None, None, None, -1))


def delivery_mail_thread():
    if not __wecom_user or not __wecom_psw:
        if _h_logging:
            _h_logging.error("no mail user or password")
        return
    while _mail_process_running:
        try:
            f, t, c, times = _mail_box_queue.get(block=True)
            if not _mail_process_running:
                break
            if times <= 0:
                continue
            server = smtplib.SMTP_SSL("smtp.exmail.qq.com", port=465)
            server.login(__wecom_user, __wecom_psw)
            server.sendmail(f, t, c)
            server.quit()
        except Exception as e:
            times -= 1
            _mail_box_queue.put_nowait((f, t, c, times))
            if _h_console_out:
                print(e)
            _h_logging.error(str(e))
    _h_logging.info("mailing thread stopped")


def alert_by_email(email, content, title="warning"):
    letter = MIMEText(content + "\n\n\n\t\tsent from Hawk, Canary", "plain", "utf-8")
    letter["Subject"] = title
    letter["From"] = "Hawk<%s>" % __wecom_user
    letter["To"] = email
    try:
        _mail_box_queue.put_nowait(
            ("<%s>" % __wecom_user, email, letter.as_string(), 3)
        )
        return True
    except Exception as e:
        if _h_console_out:
            print(e)
        _h_logging.error(str(e))
        return False


def pub_to_device(dev, msg):
    if not _h_client or not _h_client.is_connected():
        return False
    _h_client.publish(r"lily/%s/cmd" % dev, msg)
    return True


def exec_action(context: dict, u: str, a: str):
    try:
        a = re.sub(_blank_replacer, " ", a)
        if "while" in a or "for" in a or "import" in a:
            _h_logging.error(a + ": invalid keywords")
            return None
        local_dict = {
            "me": u,
            "action": a,
            "alert": alert_by_email,
            "pub": pub_to_device,
        }
        exec(a, context, local_dict)
        return True
    except Exception as e:
        if _h_console_out:
            print(e)
        _h_logging.error(a + ", " + str(e))
        return None


def eval_condition(context: dict, c: str):
    try:
        if "while" in c or "for" in c or "import" in c:
            _h_logging.error(c + ": invalid keywords")
            return None
        c = re.sub(_blank_replacer, " ", c)
        local_dict = {"condition": c}
        ok = eval(c, context, local_dict)
        return ok
    except Exception as e:
        _h_logging.error(c + ", " + str(e))
        return None


def hawkeye_sweep_device(cursor, device: str, data: dict):
    """note:data will be modified after this function"""
    fetch_rules = (
        "select id,user,`condition`,action,times,need_times,trigger_time,min_period,type,ext from hawkeye where device='%s' and type!='off';"
        % device
    )
    cursor.execute(fetch_rules)
    rules = cursor.fetchall()
    if rules:
        data["device"] = device  # new context,device info
        data["re"] = re  # re module
        data["datetime"] = dt
        now = dt.datetime.now()  # date time info
        data["year"] = now.year
        data["month"] = now.month
        data["day"] = now.day
        data["hour"] = now.hour
        data["minute"] = now.minute
        data["second"] = now.second
    for i, u, c, a, t, nt, tt, mp, r_type, ext in rules:
        if type(tt) is str:
            tt = dt.datetime.fromisoformat(tt)
        r = eval_condition(data, c)
        if r is None:
            deal_rule = "UPDATE hawkeye set error_count=error_count+1 WHERE id=%d;" % i
            cursor.execute(deal_rule)
        if not r:  # reset continue times
            deal_rule = "UPDATE hawkeye set times=0 WHERE id=%d;" % (i)
            cursor.execute(deal_rule)
            continue
        if t + 1 < nt:  # continue times++
            deal_rule = "UPDATE hawkeye set times=times+1 WHERE id=%d;" % (i)
            cursor.execute(deal_rule)
            continue
        # too short time
        if tt and tt + dt.timedelta(minutes=mp) > dt.datetime.now():
            continue
        try:
            last_data = json.loads(ext)
        except Exception:
            last_data = {}
        data["last"] = last_data
        ok = exec_action(data, u, a)
        if ok is None:
            last_data["error"] = "error"
            deal_rule = "UPDATE hawkeye set error_count=error_count+1 WHERE id=%d;" % (
                i
            )
            cursor.execute(deal_rule)
        if r_type == "once":  # delete this rule
            deal_rule = "delete from hawkeye WHERE id=%d;" % i
        elif r_type == "onceoff":
            deal_rule = (
                "UPDATE hawkeye set times=0,count=count+1,trigger_time=datetime('now','localtime'),ext='%s',type='off' WHERE id=%d;"
                % (json.dumps(last_data), i)
            )
        else:  # set times,trigger_time,count,
            deal_rule = (
                "UPDATE hawkeye set times=0,count=count+1,trigger_time=datetime('now','localtime'),ext='%s' WHERE id=%d;"
                % (json.dumps(last_data), i)
            )
        cursor.execute(deal_rule)


def hawkeye_sweep(cursor):
    """
    1.  get msg from buffer
    2.  exec script for each device
    3.  update count times if condition met
    4.  if in the time window, then send a email and reset the last_send_time and count
    commit after this
    """
    fetch_msg = "select device,tag from polls_buffer;"
    try:
        cursor.execute(fetch_msg)
        msg = cursor.fetchall()
        for device, tag in msg:
            try:
                d = json.loads(tag)
                if not d:
                    continue
                hawkeye_sweep_device(cursor, device, d)
            except Exception:
                continue
    except Exception as e:
        _h_logging.error(e)
